/* Copyright (C) 2006 EBI This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the itmplied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package org.biomart.common.view.gui; import java.awt.AlphaComposite; import java.awt.Graphics2D; import java.awt.Insets; import java.awt.Point; import java.awt.Rectangle; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.StringSelection; import java.awt.datatransfer.Transferable; import java.awt.dnd.Autoscroll; import java.awt.dnd.DnDConstants; import java.awt.dnd.DragGestureEvent; import java.awt.dnd.DragGestureListener; import java.awt.dnd.DragGestureRecognizer; import java.awt.dnd.DragSource; import java.awt.dnd.DragSourceDragEvent; import java.awt.dnd.DragSourceDropEvent; import java.awt.dnd.DragSourceEvent; import java.awt.dnd.DragSourceListener; import java.awt.dnd.DropTarget; import java.awt.dnd.DropTargetContext; import java.awt.dnd.DropTargetDragEvent; import java.awt.dnd.DropTargetDropEvent; import java.awt.dnd.DropTargetEvent; import java.awt.dnd.DropTargetListener; import java.awt.image.BufferedImage; import java.util.Hashtable; import java.util.Vector; import javax.swing.JComponent; import javax.swing.JTree; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreeModel; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; /** * A {@link JTree} that allows drag-and-drop and autoscroll when doing so. * * @author Richard Holland <holland@ebi.ac.uk> * @version $Revision: 1.1 $, $Date: 2007-11-09 11:36:28 $, modified by * $Author: rh4 $ * @since 0.7 */ public abstract class DraggableJTree extends JTree implements Autoscroll, DragSourceListener, DragGestureListener, DropTargetListener { private static final long serialVersionUID = 1L; private int margin = 12; private TreePath transferStartPath; private TreePath transferStopPath; private boolean dragValid = false; private final DragSource source = new DragSource(); private final DropTarget target = new DropTarget(this, this); private final DragGestureRecognizer recognizer = this.source .createDefaultDragGestureRecognizer(this, DnDConstants.ACTION_COPY_OR_MOVE, this); private BufferedImage image = null; private final Rectangle rect2D = new Rectangle(); /** * See {@link JTree#JTree()}. */ public DraggableJTree() { super(); // To prevent unused-variable warnings. if (this.target == null || this.recognizer == null) { } } /** * See {@link JTree#JTree(Hashtable)}. * * @param value */ public DraggableJTree(final Hashtable value) { super(value); } /** * See {@link JTree#JTree(Object[])}. * * @param value */ public DraggableJTree(final Object[] value) { super(value); } /** * See {@link JTree#JTree(TreeModel)}. * * @param newModel */ public DraggableJTree(final TreeModel newModel) { super(newModel); } /** * See {@link JTree#JTree(TreeNode, boolean)}. * * @param root * @param asksAllowsChildren */ public DraggableJTree(final TreeNode root, final boolean asksAllowsChildren) { super(root, asksAllowsChildren); } /** * See {@link JTree#JTree(TreeNode)}. * * @param root */ public DraggableJTree(final TreeNode root) { super(root); } /** * See {@link JTree#JTree(Vector)}. * * @param value */ public DraggableJTree(final Vector value) { super(value); } /** * Set the auto-scroll detection margin size. * * @param margin * the size. */ public void setMargin(final int margin) { this.margin = margin; } /** * Get the auto-scroll detection margin size. * * @return the size. */ public int getMargin() { return this.margin; } public void autoscroll(final Point p) { int realrow = this.getRowForLocation(p.x, p.y); final Rectangle outer = this.getBounds(); realrow = p.y + outer.y <= this.margin ? realrow < 1 ? 0 : realrow - 1 : realrow < this.getRowCount() - 1 ? realrow + 1 : realrow; this.scrollRowToVisible(realrow); } public Insets getAutoscrollInsets() { final Rectangle outer = this.getBounds(); final Rectangle inner = this.getParent().getBounds(); return new Insets(inner.y - outer.y + this.margin, inner.x - outer.x + this.margin, outer.height - inner.height - inner.y + outer.y + this.margin, outer.width - inner.width - inner.x + outer.x + this.margin); } public void dragDropEnd(final DragSourceDropEvent dsde) { if (dsde.getDropSuccess()) this.dragCompleted(dsde.getDropAction(), this.transferStartPath, this.transferStopPath); } public void dragEnter(final DragSourceDragEvent dsde) { this.updateCursor(dsde); } public void dragExit(final DragSourceEvent dse) { dse.getDragSourceContext().setCursor(DragSource.DefaultMoveNoDrop); } public void dragOver(final DragSourceDragEvent dsde) { this.updateCursor(dsde); } private void updateCursor(final DragSourceDragEvent dsde) { final int action = dsde.getDropAction(); if (action == DnDConstants.ACTION_COPY) dsde.getDragSourceContext().setCursor(DragSource.DefaultCopyDrop); else if (action == DnDConstants.ACTION_MOVE) dsde.getDragSourceContext().setCursor(DragSource.DefaultMoveDrop); else dsde.getDragSourceContext().setCursor(DragSource.DefaultMoveNoDrop); } public void dropActionChanged(final DragSourceDragEvent dsde) { this.updateCursor(dsde); } public void dragGestureRecognized(final DragGestureEvent dge) { final TreePath path = this.getSelectionPath(); // Is path draggable? Abstract method will check for us. // We can't move an empty selection. if (path == null || !this.isValidDragPath(path)) return; final Rectangle pathBounds = this.getPathBounds(path); final JComponent lbl = (JComponent) this.getCellRenderer() .getTreeCellRendererComponent( this, path.getLastPathComponent(), false, this.isExpanded(path), ((DefaultTreeModel) this.getModel()).isLeaf(path .getLastPathComponent()), 0, false); lbl.setBounds(pathBounds); this.image = new BufferedImage(lbl.getWidth(), lbl.getHeight(), BufferedImage.TYPE_INT_ARGB_PRE); final Graphics2D graphics = this.image.createGraphics(); graphics.setComposite(AlphaComposite.getInstance( AlphaComposite.SRC_OVER, 0.5f)); lbl.paint(graphics); graphics.dispose(); this.transferStartPath = path; this.source.startDrag(dge, DragSource.DefaultMoveNoDrop, this.image, new Point(0, 0), new StringSelection( "MartRunner JobPlan Tree Drag"), this); } private TreePath getPathForEvent(final DropTargetDragEvent dtde) { final Point p = dtde.getLocation(); final DropTargetContext dtc = dtde.getDropTargetContext(); final JTree tree = (JTree) dtc.getComponent(); return tree.getClosestPathForLocation(p.x, p.y); } private TreePath getPathForEvent(final DropTargetDropEvent dtde) { final Point p = dtde.getLocation(); final DropTargetContext dtc = dtde.getDropTargetContext(); final JTree tree = (JTree) dtc.getComponent(); return tree.getClosestPathForLocation(p.x, p.y); } public void dragEnter(final DropTargetDragEvent dtde) { this.updateDrag(dtde); } public void dragExit(final DropTargetEvent dte) { this.clearImage(); } public void dragOver(final DropTargetDragEvent dtde) { this.updateDrag(dtde); } private void updateDrag(final DropTargetDragEvent dtde) { this.paintImage(dtde.getLocation()); // Is path droppable? Abstract method will check for us. this.dragValid = this.isValidDropPath(this.getPathForEvent(dtde)); if (!this.dragValid) dtde.rejectDrag(); else dtde.acceptDrag(dtde.getDropAction()); } public void drop(final DropTargetDropEvent dtde) { try { this.clearImage(); this.transferStopPath = this.getPathForEvent(dtde); // Is path droppable? Abstract method will check for us. if (!this.isValidDropPath(this.transferStopPath)) { dtde.rejectDrop(); dtde.dropComplete(false); return; } // Complete the drag. final Transferable tr = dtde.getTransferable(); final DataFlavor[] flavors = tr.getTransferDataFlavors(); for (int i = 0; i < flavors.length; i++) if (tr.isDataFlavorSupported(flavors[i])) { dtde.acceptDrop(dtde.getDropAction()); dtde.dropComplete(true); return; } dtde.rejectDrop(); } catch (final Throwable t) { t.printStackTrace(); dtde.rejectDrop(); dtde.dropComplete(false); } } private final void paintImage(final Point pt) { this.paintImmediately(this.rect2D.getBounds()); this.rect2D.setRect((int) pt.getX(), (int) pt.getY(), this.image .getWidth(), this.image.getHeight()); this.getGraphics().drawImage(this.image, (int) pt.getX(), (int) pt.getY(), this); } private final void clearImage() { this.paintImmediately(this.rect2D.getBounds()); } public void dropActionChanged(final DropTargetDragEvent dtde) { this.updateDrag(dtde); } /** * Check to see if the given path is a valid starting point. * * @param path * the path to the node being dragged. * @return <tt>true</tt> if it can be dragged. */ public abstract boolean isValidDragPath(final TreePath path); /** * Check to see if the given path is a valid stopping point. * * @param path * the path to the node being dropped onto. * @return <tt>true</tt> if it can be dropped onto. */ public abstract boolean isValidDropPath(final TreePath path); /** * Drag has completed. * * @param action * the type of drag. * @param from * what was dragged. * @param to * where it was dragged to. */ public abstract void dragCompleted(final int action, final TreePath from, final TreePath to); }